<!DOCTYPE html>
<html lang="en">
    <head>
        <title>three.js webgl - GLTFloader</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            html,body{margin: 0;width: 100%;height: 100%;overflow: hidden;}
        </style>
    </head>

    <body>
        <script type="importmap">
            {
                "imports": {
                    "three": "./js/threejs/three-r165.min.js",
                    "three/addons/": "./js/threejs/jsm-r165/"
                }
            }
        </script>

        <script type="module">

            import * as THREE from 'three';

            import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
            import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
            import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
            import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
            import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
            import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
            

            let camera, scene, renderer;
            let pointer, raycaster, INTERSECTED, OLDMATERIAL, CABINETSECTED;

            const clock = new THREE.Clock();
            let mixer, animations, action;
            let anum = 0;

            var servers = [];
            let cabinet;

            let composer, outlinePass;
            let selectedObjects = [];

            init();
            animate();

            function init() {

                const container = document.createElement( 'div' );
                document.body.appendChild( container );

                camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 10000 );
                camera.position.set( 20, 100, 400 );

                raycaster = new THREE.Raycaster();
                pointer = new THREE.Vector2();

                // 视角
                const w = 1920;
                const h = 1080;
                const fullWidth = w * 3;
                const fullHeight = h * 2;
                camera.setViewOffset( fullWidth, fullHeight, w * 1, 200, w, h );

                scene = new THREE.Scene();
                scene.background = new THREE.Color( '#1f2940' );

                // 环境光
                const ambientLight = new THREE.AmbientLight(0xffffff, 6); 
                scene.add(ambientLight);  
                // 平行光
                const dirLight = new THREE.DirectionalLight( 0xffffff, 2 );
                dirLight.position.set( 1000, -1000, 1000 );
                dirLight.castShadow = true;
                scene.add( dirLight );

                // model
                // 机柜
                const loader = new GLTFLoader().setPath( 'models/gltf/' );
                loader.load( 'cabinet.glb', async function ( gltf ) {
                    console.log(gltf)
                    cabinet = gltf.scene;
                    cabinet.scale.setScalar(30);
                    mixer = new THREE.AnimationMixer( cabinet );
                    animations = gltf.animations;
                    await renderer.compileAsync( cabinet, camera, scene );
                    scene.add( cabinet );
                    render();
                } );

                // 网络设备
                loader.load( 'internet_server/scene.gltf', async function ( gltf ) {
                    const gltfModel = gltf.scene;
                    gltfModel.scale.setScalar(1.02);
                    // gltfModel.position.x = -8;
                    gltfModel.position.y = 45;
                    gltfModel.position.z = 5;
                    gltfModel.rotateY(-Math.PI / 2);
                    await renderer.compileAsync( gltfModel, camera, scene );
                    scene.add( gltfModel );
                    servers.push(gltfModel)
                    // 复制
                    const clonedObject = gltfModel.clone();                                
                    clonedObject.position.y = 35;
                    scene.add( clonedObject );
                    servers.push(clonedObject)
                    const clonedObject2 = gltfModel.clone();                                
                    clonedObject2.position.y = 30;
                    scene.add( clonedObject2 );
                    servers.push(clonedObject2)
                } );

                renderer = new THREE.WebGLRenderer( { antialias: true } );
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize( window.innerWidth, window.innerHeight );
                renderer.toneMapping = THREE.ACESFilmicToneMapping;
                renderer.toneMappingExposure = 1;
                container.appendChild( renderer.domElement );

                render();

                const controls = new OrbitControls( camera, renderer.domElement );
                controls.addEventListener( 'change', render ); // use if there is no animation loop
                controls.update();

                document.addEventListener( 'mousemove', onPointerMove );

                // 点击事件触发开/关门动画
                document.addEventListener( 'click', function ( event ) {
                    if (CABINETSECTED) {
                        const clip = animations[anum];
                        // 停止上一个动画
                        if (action) {
                            action.stop();
                        }
                        action = mixer.clipAction(clip);
                        action.clampWhenFinished = true; // 物体状态停留在动画结束的时候
                        action.loop = THREE.LoopOnce;  // 播放一次
                        action.timeScale = 1.5  // 倍速播放
                        if (anum == 1) {
                            anum = 0
                        } else {
                            anum = 1
                        }
                        action.play();
                    }
                }, false );

                // 发光描边
                composer = new EffectComposer( renderer );

                const renderPass = new RenderPass( scene, camera );
                composer.addPass( renderPass );

                outlinePass = new OutlinePass( new THREE.Vector2( window.innerWidth, window.innerHeight ), scene, camera );
                outlinePass.edgeGlow = 1 // 发光强度
                outlinePass.edgeThickness = 2 // 边缘浓度
                outlinePass.edgeStrength = 3 // 边缘的强度，值越高边框范围越大
                // outlinePass.pulsePeriod = 2 // 闪烁频率，值越大频率越低
                outlinePass.visibleEdgeColor.set('#04b87a') // 呼吸显示的颜色
                outlinePass.hiddenEdgeColor.set('#04b87a') // 不可见边缘的颜色
                composer.addPass( outlinePass );

                const outputPass = new OutputPass();
                composer.addPass( outputPass );

            }

            // 递归children获取对象
            function findObj(obj, target) {
                if (obj.uuid == target.uuid) {
                    return true
                } else if (obj.children.length) {
                    for (let i in obj.children) {
                        const result = findObj(obj.children[i], target)
                        if (result) {
                            return result
                        }
                    }
                    return false
                } else {
                    return false
                }
            }

            // 未选中还原方法
            function backStatus() {
                if ( INTERSECTED ) { 
                    // INTERSECTED.position.z -= 8
                    selectedObjects = [];
                    outlinePass.selectedObjects = selectedObjects;
                }
            }

            // 选中方法
            function newStatus(obj) {
                INTERSECTED = obj;
                // INTERSECTED.position.z += 8
                selectedObjects = [];
                selectedObjects.push( INTERSECTED );
                outlinePass.selectedObjects = selectedObjects;
            }


            // 鼠标移动选中服务器
            function onPointerMove( event ) {
                // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
                pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1;
                pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

                // 鼠标移过物体
                // 通过摄像机和鼠标位置更新射线
                raycaster.setFromCamera( pointer, camera );
                // 计算物体和射线的焦点
                const intersects = raycaster.intersectObjects(scene.children, true);
                if ( intersects.length > 0 ) {
                    // 选中服务器，开了门才能选
                    if (anum == 1) {
                        for (let i in servers) {
                            if (findObj(servers[i], intersects[0].object)) {
                                document.body.style.cursor = 'pointer';
                                if ( !INTERSECTED || INTERSECTED != servers[i] ) {
                                    backStatus();
                                    newStatus(servers[i])
                                }
                                return
                            }
                        }
                    }
                    // 选中机柜
                    if (findObj(cabinet, intersects[0].object)) {
                        document.body.style.cursor = 'pointer';
                        if ( !CABINETSECTED ) {
                            CABINETSECTED = cabinet;
                        }
                        return
                    }
                    backStatus();
                    INTERSECTED = null;
                    CABINETSECTED = null;
                    document.body.style.cursor = 'default';
                } else {
                    backStatus();
                    INTERSECTED = null;
                    CABINETSECTED = null;
                    document.body.style.cursor = 'default';
                }

            }

            function render() {

                renderer.render( scene, camera );

            }

            function animate() {
                requestAnimationFrame( animate );
                const delta = clock.getDelta();
                if ( mixer ) mixer.update( delta );
                renderer.render( scene, camera );
                composer.render();
            }

        </script>

    </body>
</html>
